﻿using System;
using System.Globalization;
using System.IO;
using System.Text;
using GeoAPI.Geometries;
using NetTopologySuite.Geometries;

namespace NetTopologySuite.IO.KML
{
	public class KMLWriter
	{
		public static string WriteGeometry(IGeometry geometry, double z)
		{
			return new KMLWriter
			{
				Z = z
			}.Write(geometry);
		}

		public KMLWriter()
		{
			this.CreateDefaultFormatter();
		}

		private void CreateDefaultFormatter()
		{
			this.CreateFormatter(-1);
		}

		public static string WriteGeometry(IGeometry geometry, double z, int precision, bool extrude, string altitudeMode)
		{
			return new KMLWriter
			{
				Z = z,
				Precision = precision,
				Extrude = extrude,
				AltitudeMode = altitudeMode
			}.Write(geometry);
		}

		public string LinePrefix
		{
			get
			{
				return this._linePrefix;
			}
			set
			{
				this._linePrefix = value;
			}
		}

		public int MaxCoordinatesPerLine
		{
			get
			{
				return this._maxCoordinatesPerLine;
			}
			set
			{
				this._maxCoordinatesPerLine = System.Math.Max(1, value);
			}
		}

		public double Z
		{
			get
			{
				return this._z;
			}
			set
			{
				this._z = value;
			}
		}

		public bool Extrude
		{
			get
			{
				return this._extrude;
			}
			set
			{
				this._extrude = value;
			}
		}

		public bool Tesselate
		{
			get
			{
				return this._tesselate;
			}
			set
			{
				this._tesselate = value;
			}
		}

		public string AltitudeMode
		{
			get
			{
				return this._altitudeMode;
			}
			set
			{
				this._altitudeMode = value;
			}
		}

		public int Precision
		{
			get
			{
				return this._formatter.NumberDecimalDigits;
			}
			set
			{
				this.CreateFormatter(value);
			}
		}

		private void CreateFormatter(int precision)
		{
			IPrecisionModel precisionModel = (precision < 0) ? new PrecisionModel(PrecisionModels.Floating) : new PrecisionModel((double)precision);
			this._formatter = WKTWriter.CreateFormatter(precisionModel);
			string arg = WKTWriter.StringOfChar('#', this._formatter.NumberDecimalDigits);
			this._format = string.Format("0.{0}", arg);
		}

		public string Write(IGeometry geom)
		{
			StringBuilder stringBuilder = new StringBuilder();
			this.Write(geom, stringBuilder);
			return stringBuilder.ToString();
		}

		public void Write(IGeometry geom, TextWriter writer)
		{
			string value = this.Write(geom);
			writer.Write(value);
		}

		public void Write(IGeometry geom, StringBuilder sb)
		{
			this.WriteGeometry(geom, 0, sb);
		}

		private void WriteGeometry(IGeometry g, int level, StringBuilder sb)
		{
			if (g is IPoint)
			{
				this.WritePoint(g as IPoint, level, sb);
				return;
			}
			if (g is ILinearRing)
			{
				this.WriteLinearRing(g as ILinearRing, level, sb, true);
				return;
			}
			if (g is ILineString)
			{
				this.WriteLineString(g as ILineString, level, sb);
				return;
			}
			if (g is IPolygon)
			{
				this.WritePolygon(g as IPolygon, level, sb);
				return;
			}
			if (g is IGeometryCollection)
			{
				this.WriteGeometryCollection(g as IGeometryCollection, level, sb);
				return;
			}
			throw new ArgumentException(string.Format("Geometry type not supported: {0}", g.GeometryType));
		}

		private void StartLine(string text, int level, StringBuilder sb)
		{
			if (this.LinePrefix != null)
			{
				sb.Append(this.LinePrefix);
			}
			sb.Append(WKTWriter.StringOfChar(' ', 2 * level));
			sb.Append(text);
		}

		private string GeometryTag(string geometryName)
		{
			StringBuilder stringBuilder = new StringBuilder();
			stringBuilder.Append("<");
			stringBuilder.Append(geometryName);
			stringBuilder.Append(">");
			return stringBuilder.ToString();
		}

		private void WriteModifiers(int level, StringBuilder sb)
		{
			if (this.Extrude)
			{
				this.StartLine("<extrude>1</extrude>\n", level, sb);
			}
			if (this.Tesselate)
			{
				this.StartLine("<tesselate>1</tesselate>\n", level, sb);
			}
			if (this.AltitudeMode != null)
			{
				string text = string.Format("<altitudeMode>{0}</altitudeMode>\n", this.AltitudeMode);
				this.StartLine(text, level, sb);
			}
		}

		private void WritePoint(IPoint p, int level, StringBuilder sb)
		{
			this.StartLine(this.GeometryTag("Point") + "\n", level, sb);
			this.WriteModifiers(level, sb);
			this.Write(new Coordinate[]
			{
				p.Coordinate
			}, level + 1, sb);
			this.StartLine("</Point>\n", level, sb);
		}

		private void WriteLineString(ILineString ls, int level, StringBuilder sb)
		{
			this.StartLine(this.GeometryTag("LineString") + "\n", level, sb);
			this.WriteModifiers(level, sb);
			this.Write(ls.Coordinates, level + 1, sb);
			this.StartLine("</LineString>\n", level, sb);
		}

		private void WriteLinearRing(ILinearRing lr, int level, StringBuilder sb, bool writeModifiers)
		{
			this.StartLine(this.GeometryTag("LinearRing") + "\n", level, sb);
			if (writeModifiers)
			{
				this.WriteModifiers(level, sb);
			}
			this.Write(lr.Coordinates, level + 1, sb);
			this.StartLine("</LinearRing>\n", level, sb);
		}

		private void WritePolygon(IPolygon p, int level, StringBuilder sb)
		{
			this.StartLine(this.GeometryTag("Polygon") + "\n", level, sb);
			this.WriteModifiers(level, sb);
			this.StartLine("  <outerBoundaryIs>\n", level, sb);
			this.WriteLinearRing((ILinearRing)p.ExteriorRing, level + 1, sb, false);
			this.StartLine("  </outerBoundaryIs>\n", level, sb);
			for (int i = 0; i < p.NumInteriorRings; i++)
			{
				this.StartLine("  <innerBoundaryIs>\n", level, sb);
				this.WriteLinearRing((ILinearRing)p.GetInteriorRingN(i), level + 1, sb, false);
				this.StartLine("  </innerBoundaryIs>\n", level, sb);
			}
			this.StartLine("</Polygon>\n", level, sb);
		}

		private void WriteGeometryCollection(IGeometryCollection gc, int level, StringBuilder sb)
		{
			this.StartLine("<MultiGeometry>\n", level, sb);
			for (int i = 0; i < gc.NumGeometries; i++)
			{
				this.WriteGeometry(gc.GetGeometryN(i), level + 1, sb);
			}
			this.StartLine("</MultiGeometry>\n", level, sb);
		}

		private void Write(Coordinate[] coords, int level, StringBuilder sb)
		{
			this.StartLine("<coordinates>", level, sb);
			bool flag = false;
			for (int i = 0; i < coords.Length; i++)
			{
				if (i > 0)
				{
					sb.Append(" ");
				}
				if (flag)
				{
					this.StartLine("  ", level, sb);
					flag = false;
				}
				this.Write(coords[i], sb);
				if ((i + 1) % this.MaxCoordinatesPerLine == 0 && i < coords.Length - 1)
				{
					sb.Append("\n");
					flag = true;
				}
			}
			sb.Append("</coordinates>\n");
		}

		private void Write(Coordinate p, StringBuilder sb)
		{
			this.Write(p.X, sb);
			sb.Append(",");
			this.Write(p.Y, sb);
			double z = p.Z;
			if (!double.IsNaN(this.Z))
			{
				z = this.Z;
			}
			if (!double.IsNaN(z))
			{
				sb.Append(",");
				this.Write(z, sb);
			}
		}

		private void Write(double d, StringBuilder sb)
		{
			string value = d.ToString(this._format, this._formatter);
			sb.Append(value);
		}

		public const string AltitudeModeClampToGround = "clampToGround ";

		public const string AltitudeModeRelativeToGround = "relativeToGround  ";

		public const string AltitudeModeAbsolute = "absolute";

		private const int IndentSize = 2;

		private const string CoordinateSeparator = ",";

		private const string TupleSeparator = " ";

		private string _linePrefix;

		private int _maxCoordinatesPerLine = 5;

		private double _z = double.NaN;

		private bool _extrude;

		private bool _tesselate;

		private string _altitudeMode;

		private NumberFormatInfo _formatter;

		private string _format;
	}
}
